﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mono.Cecil;
using Mono.Cecil.Cil;
using System.Collections;
using devtm.Aop.Copy;

namespace devtm.Aop.Extensions
{
    
    public static class MethodDefinitionExt
    {

        #region Method



        public static IEnumerable<CustomAttribute> GetCustomAttribute(this MethodDefinition method, Type typeAttribute)
        {
            foreach (CustomAttribute attr in method.CustomAttributes)
                if (attr.AttributeType.Name == typeAttribute.Name && 
                    attr.AttributeType.Namespace == typeAttribute.Namespace)
                    yield return attr;
        }



   
        public static string GetFullName(this MethodDefinition methodDefinition)
        {
            return string.Format("{0}.{1}", methodDefinition.DeclaringType.FullName, methodDefinition.Name);
        }


        public static void MoveMethodBody(this MethodDefinition m1, MethodDefinition method)
        {
            //on copie les instruction
            var instructions = m1.Body.Instructions.ToList();

            // Les variables
            var variables = m1.Body.Variables.ToList();

            // On supprime
            m1.Body.Variables.Clear();
            m1.Body.Instructions.Clear();

            foreach (VariableDefinition v1 in variables)
                method.Body.Variables.Add(v1);

            foreach (Instruction item6 in instructions)
                method.Body.Instructions.Add(item6);

        }

        public static void CopyReferenceInstructionFromMethodBody(this MethodDefinition m1, MethodDefinition method)
        {

            //on copie les instruction
            var instructions = m1.Body.Instructions.ToList();

            // Les variables
            var variables = m1.Body.Variables.ToList();

            foreach (VariableDefinition v1 in variables)
                method.Body.Variables.Add(v1);

            ModuleDefinition _module = method.Module;

            foreach (Instruction item6 in instructions)
            {

                object operand = item6.Operand;

                if (operand is MethodReference)
                    item6.Operand = _module.Import(operand as MethodReference);
                else if (operand is TypeReference)
                    item6.Operand = _module.Import(operand as TypeReference);
                else if (operand is VariableDefinition)
                    item6.Operand = operand as VariableDefinition;
                else if (operand is ParameterDefinition)
                    item6.Operand = operand as ParameterDefinition;
                else if (operand is FieldDefinition)
                    item6.Operand = _module.Import(operand as FieldDefinition);
                else
                {

                }
                method.Body.Instructions.Add(item6);
            }

        }

        public static void CloneMethodBody(this MethodDefinition m1, MethodDefinition method)
        {

            ModuleDefinition _module = method.Module;

            Hashtable variables = new Hashtable();
            foreach (VariableDefinition v1 in m1.Body.Variables)
            {
                var v = new VariableDefinition(v1.Name, _module.Import(v1.VariableType));
                method.Body.Variables.Add(v);
                variables.Add(v1, v);
            }


            foreach (Instruction i in m1.Body.Instructions)
            {

                Instruction i2 = null;

                OpCode opcode = i.OpCode;
                object operand = i.Operand;


                if (operand == null)
                    i2 = Instruction.Create(opcode);
                else if (operand is byte)
                    i2 = Instruction.Create(opcode, (byte)operand);
                else if (operand is double)
                    i2 = Instruction.Create(opcode, (double)operand);
                else if (operand is float)
                    i2 = Instruction.Create(opcode, (float)operand);
                else if (operand is int)
                    i2 = Instruction.Create(opcode, (int)operand);
                else if (operand is long)
                    i2 = Instruction.Create(opcode, (long)operand);
                else if (operand is sbyte)
                    i2 = Instruction.Create(opcode, (sbyte)operand);
                else if (operand is string)
                    i2 = Instruction.Create(opcode, operand as string);

                else if (operand is TypeReference)
                    i2 = Instruction.Create(opcode, _module.Import(operand as TypeReference));

                else if (operand is VariableDefinition)
                    Instruction.Create(opcode, variables[operand] as VariableDefinition);

                else if (operand is ParameterDefinition)
                    Instruction.Create(opcode, method.Parameters[(operand as ParameterDefinition).Index]);

                else if (operand is MethodReference)
                    i2 = Instruction.Create(opcode, _module.Import(operand as MethodReference));

                else if (operand is FieldReference)
                {
                    FieldDefinition f1 = null;
                    foreach (FieldDefinition f in method.DeclaringType.Fields)
                        if (f.Name == (operand as FieldReference).Name)
                        {
                            f1 = f;
                            break;
                        }

                    if (f1 == null)
                        throw new Exception("the block can't be copied because all the field not exists in the target.");

                    Instruction.Create(opcode, f1);

                }
                else if (operand is Instruction)
                {

                    //Instruction.Create(opcode, operand as Instruction);

                }

                //Instruction.Create(opcode, operand as Instruction[]);
                //Instruction.Create(opcode, operand as CallSite);


                i.SequencePoint.CopyTo(i2);
                method.Body.Instructions.Add(i2);
            }


        }

        #endregion



        public static MethodDefinition OverrideMethod(this MethodDefinition method, TypeDefinition targetDeclaringType)
        {            

            CopyMethod cm = new CopyMethod(method);

            var m = cm.CloneMethod(method.DeclaringType, targetDeclaringType, false);

            m.Overrides.Add(targetDeclaringType.Module.Import(method));

            targetDeclaringType.Methods.Add(m);

            return m;
        }

    }
}
